package Client;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;
import Common.Constants;
import Common.Utils;

public class MyService implements Service{


	

	public boolean backupFile(CommandParameters params) throws IOException, Base64DecodingException{

		File file = new File(params.getFileName());
		if(!Utils.isFileOK(params.getFileName(),file)){
			return false;
		}

		byte[] fileInBytes = Utils.getBytesFromFile(file);
		
		//2.performs backup with signature
		int httpCode = backUpUsingSignature(fileInBytes, params);
		if (httpCode == HttpURLConnection.HTTP_NOT_FOUND){
			if(backTheFileUp(fileInBytes, params)) return true;
		}
		else if(httpCode == HttpURLConnection.HTTP_OK) {
			return true;
		}
		
		return false;
	} 

	private int backUpUsingSignature(byte[] fileInBytes, CommandParameters params) throws IOException {
		/* we want the hash of the file */
		byte[] hashedFileValue = Utils.getSHA256hash(fileInBytes);
      
		String hashBase64 = Common.Base64.encodeBytes(hashedFileValue, Common.Base64.URL_SAFE) + "\n";
		String unixFileName = Utils.ToUnixPath(params.getFileName());
		URL url = null;
		try {
			url = new URL(ClientUtils.URL_PREFIX + unixFileName + "?" + Constants.SIGNATURE + "=" + hashBase64);
			
		} catch (MalformedURLException e){
			System.out.println("the URL resulted from your request is malformed");
			e.printStackTrace();
		}
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();
		addUserToConnectionHeaders(connection,params.getUserName());
		connection.setRequestMethod("PUT");
		try {
			connection.connect();
		} catch (IOException e) {
			System.out.println("could not connect to server at Host: " +  url.getHost() + " port: " + url.getPort());
			e.printStackTrace();
		}
		int rc = connection.getResponseCode();

		if (rc == HttpURLConnection.HTTP_OK) {//able to connect to server and find signature
			return rc;
		}
		else if(rc == HttpURLConnection.HTTP_NOT_FOUND){//the server didn't find the signature
			return rc;
		}
		else if(rc == HttpURLConnection.HTTP_BAD_REQUEST){//no user name entered
			System.out.println("User Name is needed to proceed with this action");
			return rc;
		}
		else if (rc == HttpURLConnection.HTTP_INTERNAL_ERROR){
			System.out.println("ERROR: internal server error");
			return rc;
		}
		else return 0;//unexpected
		
	}

	public boolean backupDir(CommandParameters params) throws IOException, Base64DecodingException{

		File dir = new File(params.getDirName());

		if(!Utils.isDirOK(params.getDirName(),dir)){
			return false;
		}

		File [] files = dir.listFiles();
		for (int i = 0; i < files.length; i++){
			if (files[i].isFile()){
				CommandParameters fileParams = new CommandParameters(files[i].toString(), null, params.getUserName());
				if(!backupFile(fileParams)){
					return false;
				}
			}
		}
		return true;
	}


	public boolean checkDir(CommandParameters params) throws  Base64DecodingException, IOException {
		
		if(params.getDirName().charAt(1) == ':'){
			ClientUtils.setHomeDriver(params.getDirName().substring(0, 2));
		}
		
		
		String unixDirName = Utils.ToUnixPath(params.getDirName());
		unixDirName = addDirSuffix(unixDirName);
		URL url = null;
		try {
			url = new URL(ClientUtils.URL_PREFIX + unixDirName + "?" + Constants.CHECK);// + Utils.URL_SEPARATOR);
		} catch (MalformedURLException e) {
			System.out.println("the URL resulted from your request is malformed");
			e.printStackTrace();
		}
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();

		addUserToConnectionHeaders(connection,params.getUserName());
		connection.setRequestMethod("GET");
		try {
			connection.connect();
		} catch (IOException e) {
			System.out.println("could not connect to server at Host: " +  url.getHost() + " port: " + url.getPort());
			e.printStackTrace();
		}

		int rc = connection.getResponseCode();
		if (rc == HttpURLConnection.HTTP_OK){
			byte[] body = Utils.readInputStream(connection.getInputStream());
			String bodyString = new String(body);
			String[] fileList = bodyString.split("\n");

			for (String line: fileList){
				//parse file name
				String filename = line.substring(0, line.indexOf(","));
				filename = filename.trim();			//remove leading+following white-spaces
				String localFilename = Utils.toLocalPath(filename);
				System.out.print("file name: " + localFilename + ",");
				
				//DEBUG: started changing here
				String restOfLine = line.substring(line.indexOf(",") + 1).trim();

				// parse snapshot number(s)
				String snapshotNums = restOfLine.substring(0, restOfLine.lastIndexOf(","));
				snapshotNums = snapshotNums.replace(',', ' ');
				System.out.print(" snapshots: " + snapshotNums +", ");
				
				//parse hash bytes
				String Base64hash = restOfLine.substring(restOfLine.lastIndexOf(",") + 1, restOfLine.length()).trim();
				byte[] hash = Common.Base64.decode(Base64hash, Common.Base64.URL_SAFE);
				System.out.println("hash: " + new String(hash));

			}
			return true;
		}
		else if(rc == HttpURLConnection.HTTP_BAD_REQUEST){//no user name entered
			System.out.println("User Name is needed to proceed with this action");
			return false;
		}
		else if (rc == HttpURLConnection.HTTP_NOT_FOUND) {
			printUnavailableResourceMsg(params.getDirName());
			return false;
		}
		else if (rc == HttpURLConnection.HTTP_INTERNAL_ERROR){
			System.out.println("server internal error");
			return false;

		}
		return false;
	}

	public boolean listDir(CommandParameters params) throws IOException {

		
		String[] list = getList(params);
			
		if (list != null){	
			for (String filename : list){
				String localFilename = Utils.toLocalPath(filename);
				if(filename.length() >0){
					System.out.println(localFilename);
				}
			}
			return true;
		}
		

		return false;	
	}

	private String addDirSuffix(String unixFileName) {
		if (!unixFileName.endsWith(Utils.URL_SEPARATOR)){
			unixFileName = unixFileName.concat("/");
		}
		return unixFileName;
	}

	public boolean restoreDir(CommandParameters params) throws IOException {


		String[] list = getList(params);
		if (list == null){
			return false;
		}

		File dir = new File(params.getDirName());
		
		dir.mkdirs();
		for (int i = 0; i < list.length; i++){
				CommandParameters fileParams = new CommandParameters(list[i], null, params.getUserName(),params.getSnapshotID());
				if(!restoreFile(fileParams)){
					return false;
				}
		}

		return true;
	}



	public boolean restoreFile(CommandParameters params) throws IOException {

		String unixFileName = Utils.ToUnixPath(params.getFileName());

		String snapshotID = params.getSnapshotID();
		URL url;
		if(snapshotID != null){
			url = new URL(ClientUtils.URL_PREFIX + unixFileName + "?" + Constants.SNAPSHOT + "=" + snapshotID);
		}
		else{
			url = new URL(ClientUtils.URL_PREFIX + unixFileName);
		}
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();

		addUserToConnectionHeaders(connection,params.getUserName());
		connection.setRequestMethod("GET");
		try {
			connection.connect();
		} catch (IOException e) {
			System.out.println("could not connect to server at Host: " +  url.getHost() + " port: " + url.getPort());
			e.printStackTrace();
		}
		int rc = connection.getResponseCode();
		if (rc == HttpURLConnection.HTTP_OK) {
		
			File file = new File(params.getFileName());

			file.createNewFile();
			if (!file.canWrite()) {
				throw new IOException(params.getFileName() + " does not have write permission");
			}
			byte[] FileValueFromServer = Utils.readInputStream(connection.getInputStream());
			FileOutputStream outputStream = new FileOutputStream(file);
			outputStream.write(FileValueFromServer);
		}
		else if(rc == HttpURLConnection.HTTP_BAD_REQUEST){//no user name entered
			System.out.println("User Name is needed to proceed with this action");
			return false;
		}
		else if (rc == HttpURLConnection.HTTP_NOT_FOUND){
			printUnavailableResourceMsg(params.getFileName());
			return false;
		}

		return true;
	}



	//////////////////////////////-----------------------/////////////////////////////////

	

	private String[] getList(CommandParameters params) throws IOException{
		
		String directoryName = params.getDirName();
		if(directoryName.charAt(1) == ':'){
			ClientUtils.setHomeDriver(directoryName.substring(0, 2));
		}
		
		String unixFileName = Utils.ToUnixPath(directoryName);

		//OSHRI - so we don't put an extra one
		if (!unixFileName.endsWith("/")){
			unixFileName = addDirSuffix(unixFileName);
		}
		String snapshotID = params.getSnapshotID();
		URL url;
		if(snapshotID != null){
			url = new URL(ClientUtils.URL_PREFIX + unixFileName + "?" + Constants.SNAPSHOT + "=" + snapshotID);
		}
		else{
			url = new URL(ClientUtils.URL_PREFIX + unixFileName);
		}
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();

		addUserToConnectionHeaders(connection,params.getUserName());
		connection.setRequestMethod("GET");
		try {
			connection.connect();
		} catch (IOException e) {
			System.out.println("could not connect to server at Host: " +  url.getHost() + " port: " + url.getPort());
			e.printStackTrace();
		}
		int rc = connection.getResponseCode();
		if (rc == HttpURLConnection.HTTP_OK) {
			InputStream ResponseStream = connection.getInputStream();

			//extract list from exchange body
			byte[] bodyInBytes = Utils.readInputStream(ResponseStream);
			String value = new String(bodyInBytes);

			//print the files in the list
			String[] list = value.split("\n");

				return list;
		}
		else if(rc == HttpURLConnection.HTTP_BAD_REQUEST){//no user name entered
			System.out.println("User Name is needed to proceed with this action");
			return null;
		}
		else if (rc == HttpURLConnection.HTTP_NOT_FOUND){
			printUnavailableResourceMsg(directoryName);
			return null;
		}
		else return null;
	}
	
	private static boolean checkSignature(byte[] fileInBytes ,CommandParameters params) throws Base64DecodingException, IOException{

		/* we want the hash of the file */
		byte[] hashedFileValue = Utils.getSHA256hash(fileInBytes);
      
		String unixFileName = Utils.ToUnixPath(params.getFileName());
		URL url = null;
		try {
			url = new URL(ClientUtils.URL_PREFIX + unixFileName + "?" + Constants.SIGNATURE);
			
		} catch (MalformedURLException e){
			System.out.println("the URL resulted from your request is malformed");
			e.printStackTrace();
		}
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();
		addUserToConnectionHeaders(connection,params.getUserName());
		connection.setRequestMethod("GET");
		try {
			connection.connect();
		} catch (IOException e) {
			System.out.println("could not connect to server at Host: " +  url.getHost() + " port: " + url.getPort());
			e.printStackTrace();
		}
		int rc = connection.getResponseCode();

		if (rc == HttpURLConnection.HTTP_OK) {//able to connect to server
			byte[] bodyBytes = Utils.readInputStream(connection.getInputStream());
			String body = new String(bodyBytes);
			body = body.substring(0, body.length()-1);
			bodyBytes = Common.Base64.decode(body, Common.Base64.URL_SAFE);
			System.out.println("Successfully read bytes from server regarding the hash of the file");
			boolean sameHash = Utils.md256equal(hashedFileValue, bodyBytes);

			if(sameHash){
				System.out.println(params.getFileName() + " ,This exact file already exists on the server and wont be backed up again");		
				return false;
			}
			else{
				return true;
			}
		}
		else if(rc == HttpURLConnection.HTTP_NOT_FOUND){//the server didn't find the file
			return true;
		}
		else if(rc == HttpURLConnection.HTTP_BAD_REQUEST){//no user name entered
			System.out.println("User Name is needed to proceed with this action");
			return false;
		}
		else if (rc == HttpURLConnection.HTTP_INTERNAL_ERROR){
			System.out.println("ERROR: internal server error");
			return false;
		}
		else return false;//unexpected

	}

	private static void addUserToConnectionHeaders(HttpURLConnection connection, String userName) {
		connection.addRequestProperty(Common.Constants.AUTORIZATION_FIELD, userName);		
	}

	private static boolean backTheFileUp(byte[] fileInBytes, CommandParameters params) throws IOException {

		String unixFileName = Utils.ToUnixPath(params.getFileName());
		URL url = null;
		try {
			url = new URL(ClientUtils.URL_PREFIX + unixFileName);
		} catch (MalformedURLException e) {
			System.out.println("the URL rsulted from your request is malformed");
			e.printStackTrace();
		}
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();

		addUserToConnectionHeaders(connection,params.getUserName());
		connection.setRequestMethod("PUT");
		connection.setDoOutput(true);

		OutputStream connectionStream = connection.getOutputStream();
		connectionStream.write(fileInBytes);

		try {
			connection.connect();
		} catch (IOException e) {
			System.out.println("could not connect to server at Host: " +  url.getHost() + " port: " + url.getPort());
			e.printStackTrace();
		}
		int rc = connection.getResponseCode();
		if (rc == HttpURLConnection.HTTP_OK) {
			return true;
		} 
		else if (rc == HttpURLConnection.HTTP_INTERNAL_ERROR){
			System.out.println("ERROR: internal server error");
		}
		return false;

	}
	
	private void printUnavailableResourceMsg(String resource) {
		System.out.println("Server does not have the requested resource " + resource);
		
	}

	public boolean createSnapshot(CommandParameters params) throws IOException {
		return changeSnapshot(params, false);
	}
	
	public boolean deleteSnapshot(CommandParameters params) throws IOException{
		return changeSnapshot(params, true);
	}
	
	public boolean changeSnapshot(CommandParameters params, boolean isDelete) throws IOException {
		String userRootDir = params.getDirName();

		String unixFileName = Utils.ToUnixPath(userRootDir);
		URL url = null;
		try {
			url = new URL(ClientUtils.URL_PREFIX + unixFileName + "?" + Constants.SNAPSHOT + "=" + params.getSnapshotID());
			
		} catch (MalformedURLException e){
			System.out.println("the URL resulted from your request is malformed");
			e.printStackTrace();
		}
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();
		addUserToConnectionHeaders(connection,params.getUserName());
		
		if(isDelete){
				connection.setRequestMethod("DELETE");
		}
		else connection.setRequestMethod("PUT");
		
		
		try {
			connection.connect();
		} catch (IOException e) {
			System.out.println("could not connect to server at Host: " +  url.getHost() + " port: " + url.getPort());
			e.printStackTrace();
		}
		int rc = connection.getResponseCode();

		if (rc == HttpURLConnection.HTTP_OK) {//able to connect to server and find signature
			return true;
		}
		else if (rc == HttpURLConnection.HTTP_BAD_REQUEST){
			if(isDelete){
				System.out.println("ERROR: Either this snapshot id does not exist or you tried to delete ID 0 (not allowed)");

			}
			else{
				System.out.println("ERROR: this snapshot id already exists");
			}
			return false;
		}
		else if (rc == HttpURLConnection.HTTP_INTERNAL_ERROR){
			System.out.println("ERROR: internal server error");
			return false;
		}
		
		return false;
	}

}
